package gupao.concurrency.threadpool;

import gupao.concurrency.enums.TrafficChannelEnum;
import gupao.concurrency.model.ForecastConfig;
import gupao.concurrency.model.ForcastContext;
import gupao.concurrency.model.TimeWindow;
import gupao.concurrency.model.TrafficRequest;
import gupao.concurrency.service.TrafficForecastService;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j
public class ForecastRunnableTask implements Runnable{

    public static final int STATUS_NEW = 0;
    public static final int STATUS_BEFORE_EXEC = 1;
    public static final int STATUS_EXECUTING = 2;
    public static final int STATUS_AFTER_EXEC = 3;
    public static final int STATUS_WAIT_CANCEL = 4;
    public static final int STATUS_CANCELED = 5;

    @Getter
    private final AtomicReference<ForecastConfig> forecastConfig = new AtomicReference<>();
    @Getter
    private final AtomicReference<ForecastConfig> nextForecastConfig = new AtomicReference<>();
    @Getter
    private final AtomicReference<TimeWindow> timeWindow = new AtomicReference<>();
    @Getter
    private final AtomicInteger status = new AtomicInteger(STATUS_NEW);
    @Getter
    private final AtomicInteger runTimes = new AtomicInteger(0);
    @Getter
    private volatile boolean afterExecHasRun = false;
    @Getter
    private final TrafficChannelEnum channelEnum;
    @Getter
    private final TrafficForecastService forecastService;

    public ForecastRunnableTask(TrafficChannelEnum channelEnum, ForecastConfig config,
                                TrafficForecastService forcasterService) {
        this.channelEnum = channelEnum;
        this.forecastService = forcasterService;
        this.forecastConfig.set(config);
    }

    @Override
    public void run() {
        log.info("task {} start running #run", channelEnum.name());
        setStatus(STATUS_EXECUTING);
        BlockingQueue<TrafficRequest> trafficQueue = forecastService.forecastTrafficQueue();
        while(status.get() != STATUS_CANCELED && System.currentTimeMillis() <= timeWindow.get().getEndTimestamp()){
            try{
                TrafficRequest request = trafficQueue.poll(200, TimeUnit.MILLISECONDS);
                if(request != null){
                    forecastService.singleRequest(request);
                }
            } catch (InterruptedException e){
                log.error("interrupted at channel: {}", channelEnum.name());
                Thread.currentThread().interrupt();
            }catch (Throwable t){
                log.error("error at channel {}", channelEnum.name(), t);
            }
        }
        forecastService.stopForecast();
        log.info("task {} finished run", channelEnum.name());
    }

    public void setStatus(int nextVal){
        int curVal = status.get();
        for(;;){
            if(curVal >= nextVal){
                break;
            }
            if(status.compareAndSet(curVal, nextVal)){
                break;
            }
            curVal = status.get();
        }
        if(nextVal == STATUS_AFTER_EXEC){
            afterExecHasRun = true;
        }
    }

    public void beforeExec(){
        afterExecHasRun = false;
        status.set(STATUS_BEFORE_EXEC);
        timeWindow.set(ForcastContext.getTimeWindow());
        nextForecastConfig.set(null);
        forecastService.startForecast();
        log.info("task {} has prepared #beforeExec", channelEnum.name());
    }

    public void afterExec(){
        log.info("task {} in afterExec for {} times, will stop and save result.", channelEnum.name(), runTimes.incrementAndGet());
        forecastService.stopAndSaveResult();
    }

    public void setForecastConfig(ForecastConfig config){
        forecastConfig.set(config);
    }
}
